package cn.qingyun.gis.modules.building.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import cn.qingyun.gis.exception.CommonException;
import cn.qingyun.gis.modules.building.entity.*;
import cn.qingyun.gis.modules.building.mapper.BuildingGaodeMapper;
import cn.qingyun.gis.modules.building.mapper.BuildingGatherMapper;
import cn.qingyun.gis.modules.building.mapper.BuildingProofreadMapper;
import cn.qingyun.gis.modules.building.service.IBuildingGaodeService;
import cn.qingyun.gis.modules.building.service.IBuildingProofreadAttachmentService;
import cn.qingyun.gis.modules.express.config.Exception.CustomExceptionHandler;
import cn.qingyun.gis.modules.express.constant.ExpressStatus;
import cn.qingyun.gis.modules.express.constant.RedisContant;
import cn.qingyun.gis.modules.express.entity.ExpressRegion;
import cn.qingyun.gis.modules.express.entity.OperationLog;
import cn.qingyun.gis.modules.express.mapper.OperationLogMapper;
import cn.qingyun.gis.modules.express.service.IExpressRegionService;
import cn.qingyun.gis.modules.express.util.SyncTool;
import cn.qingyun.gis.modules.init.system.constant.enums.RegionNumPermissionEnum;
import cn.qingyun.gis.modules.init.system.entity.SysLog;
import cn.qingyun.gis.modules.init.system.mapper.SysLogMapper;
import cn.qingyun.gis.modules.init.utils.CsvTool;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.csvreader.CsvReader;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKTReader;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <p>
 * 楼宇摸排高德数据 服务实现类
 * </p>
 *
 * @author guest
 * @since 2024-04-11
 */
@Service
@Slf4j
public class BuildingGaodeServiceImpl extends ServiceImpl<BuildingGaodeMapper, BuildingGaode> implements IBuildingGaodeService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private SysLogMapper sysLogMapper;
    @Resource
    private IExpressRegionService iExpressRegionService;

    @Resource
    private CustomExceptionHandler customExceptionHandler;
    @Resource
    private BuildingGaodeMapper buildingGaodeMapper;

    @Resource
    private BuildingProofreadMapper proofreadMapper;
    @Resource
    private BuildingGatherMapper gatherMapper;

    @Resource
    private OperationLogMapper operationLogMapper;
    @Resource
    private IBuildingProofreadAttachmentService attachmentService;

    @Override
    public void initData(MultipartFile file) {
        log.debug("BuildingGaodeServiceImpl initData begin");
        CompletableFuture.runAsync(() -> {
            try {
                long startTime = System.currentTimeMillis(); //获取当前内容
                CsvReader csvReader = new CsvReader(file.getInputStream(), StandardCharsets.UTF_8);
                // 读表头
                csvReader.setSafetySwitch(false);
                csvReader.readHeaders();
                List<BuildingGaodeInitDTO> imports = Lists.newArrayList();
                // 读内容
                while (csvReader.readRecord()) {
                    imports.add(CsvTool.toBean(csvReader, BuildingGaodeInitDTO.class));
                }
                int batchSize = 1000; //批次处理数据量
                if (CollUtil.isNotEmpty(imports)) {
                    // 高并发导入
                    int count = imports.size();
                    int pages = (count / batchSize) + 1; // 1000条数据启动一个线程
                    DateTime date = DateUtil.date();
                    SyncTool.doSync(pages, i -> {
                        int batchNum = i == 1 ? 0 : (i - 1) * batchSize; //开始索引
                        int batchEnd = Math.min(batchNum + batchSize, count); //结束索引 = batchEnd - 1
                        List<BuildingGaode> recordsBatch = Lists.newArrayList(); //存放批次数据
                        for (int j = batchNum; j < batchEnd; j++) {
                            BuildingGaodeInitDTO anImport = imports.get(j);
                            BuildingGaode building = new BuildingGaode();
                            BeanUtils.copyProperties(anImport, building);
                            building.setKwStatus(0); //默认为未核实状态
                            // 导入时间数据
                            building.setCreateTime(date);
                            building.setUpdateTime(date);
                            recordsBatch.add(building);
                        }
                        this.saveBatch(recordsBatch);
                        recordsBatch.clear();
                    });
                }
                log.debug("数据解析成功，当前导入需要花费{}毫秒", System.currentTimeMillis() - startTime);
            } catch (Exception e) {
                log.error("数据解析失败", e);
            }
        });

    }

    @Override
    @Transactional
    public void matchGrid() {
        CompletableFuture.runAsync(() -> {
            AtomicInteger n = new AtomicInteger();
            // 获取网格级别业务区划数据
            LambdaQueryWrapper<ExpressRegion> gridWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "4").ne(ExpressRegion::getGeometry, "");
            List<ExpressRegion> gridTotal = iExpressRegionService.list(gridWrapper);
            // 获取微网格级别业务区划数据
            LambdaQueryWrapper<ExpressRegion> miroGridWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "5").ne(ExpressRegion::getGeometry, "");
            List<ExpressRegion> miroGridTotal = iExpressRegionService.list(miroGridWrapper);
            // 获取地市级别业务区划数据
            LambdaQueryWrapper<ExpressRegion> cityWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "2").ne(ExpressRegion::getGeometry, "");
            List<ExpressRegion> cityTotal = iExpressRegionService.list(cityWrapper);
            // 获取区县级别业务区划数据
            LambdaQueryWrapper<ExpressRegion> districtsWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "3").ne(ExpressRegion::getGeometry, "");
            List<ExpressRegion> districtsTotal = iExpressRegionService.list(districtsWrapper);
            // 计算全量楼宇数据条数
            QueryWrapper<BuildingGaode> buildingGaodeQueryWrapper = new QueryWrapper<>();
            int count = this.count(buildingGaodeQueryWrapper);
            int pages = (count / 1000) + 1;
            long start = System.currentTimeMillis();

            // 根据数据总量划分数据块并启动多线程
            SyncTool.doSync(pages, i -> {
                Page<BuildingGaode> page = new Page<>(i, 1000);
                List<BuildingGaode> buildingGaodes = this.page(page, buildingGaodeQueryWrapper).getRecords();
                List<BuildingGaode> updateRecords = Lists.newArrayList();

                for (BuildingGaode data : buildingGaodes) {
                    try {
                        log.debug(String.format("====================开始匹配 %s====================", data.getId()));
                        // 如果站点经纬度都不为空或空字符串
                        if (ObjectUtil.isNotEmpty(data.getLon()) && ObjectUtil.isNotEmpty(data.getLat())) {
                            // 创建点对象
                            Point point = new GeometryFactory().createPoint(new Coordinate(data.getLon().doubleValue(), data.getLat().doubleValue()));
                            // 创建边界对象
                            Geometry geometry = null;
                            // 1.进行网格匹配
                            for (ExpressRegion gridData : gridTotal) {
                                // 创建面对象
                                geometry = new WKTReader().read(gridData.getGeometry());
                                if (geometry.contains(point)) {
                                    data.setGridId(gridData.getCode());
                                    data.setGridName(gridData.getName());
                                    break;
                                }
                            }
                            // 2.进行微网格匹配
                            for (ExpressRegion microGridData : miroGridTotal) {
                                // 创建面对象
                                geometry = new WKTReader().read(microGridData.getGeometry());
                                // 微网格匹配成功
                                if (geometry.contains(point)) {
                                    data.setMicroGridId(microGridData.getCode());
                                    data.setMicroGridName(microGridData.getName());
                                    break;
                                }
                            }
                            // 3.进行地市匹配
                            for (ExpressRegion cityData : cityTotal) {
                                // 创建面对象
                                geometry = new WKTReader().read(cityData.getGeometry());
                                // 匹配地市
                                if (geometry.contains(point)) {
                                    data.setRegionCityCode(cityData.getCode());
                                    data.setRegionCity(cityData.getName());
                                    break;
                                }
                            }
                            // 4.进行区县匹配
                            for (ExpressRegion disData : districtsTotal) {
                                geometry = new WKTReader().read(disData.getGeometry());
                                if (geometry.contains(point)) {
                                    data.setRegionDistrictsCode(disData.getCode());
                                    data.setRegionDistricts(disData.getName());
                                    break;
                                }
                            }
                        }
                        synchronized (this) {
                            n.incrementAndGet();
                            log.debug(String.format("=========== 当前匹配进度为%s / %s ===========", n, count));
                        }
                        updateRecords.add(data);
                    } catch (Exception e) {
                        customExceptionHandler.handleException(String.format("友商站点-%s,入格失败！|报错：%s", data.getPoiName(), e.getMessage()), start);
                    }
                }
                // 批量导入匹配之后数据
                this.updateBatchById(updateRecords);

            });
            log.debug(String.format("匹配网格完成，====一共处理%s楼宇数据====", count));
            // 加入日志
            SysLog sysLog = new SysLog();
            long finish = System.currentTimeMillis();
            sysLog.setLogContent("楼宇匹配数据结束");
            sysLog.setCreateTime(DateUtil.date());
            sysLog.setLogType(3);
            sysLog.setCostTime(finish - start);
            sysLog.setCreateBy("system");
            sysLog.setUserid("匹配任务");
            sysLogMapper.insert(sysLog);
        });
    }

    @Override
    public void clearData() {
        // 进行全表格式化
        this.remove(new QueryWrapper<>());
    }

    @Override
    public IPage<BuildingGaode> unProofread(BuildingProofreadQueryRequest request) {
        log.debug("BuildingGaodeServiceImpl unProofread begin");
        if (CharSequenceUtil.isNotEmpty(request.getGridType()) &&
                Boolean.TRUE.equals(RegionNumPermissionEnum.pitchRegion(request.getGridType()))) {
            // 创建分页对象
            IPage<BuildingGaode> page = new Page<>(request.getPageNum(), request.getPageSize());
            //根据网格编码+工号+未勘误（勘误状态=0）+校对类型+修改时间降序匹配查询
            QueryWrapper<BuildingGaode> queryWrapper = new QueryWrapper<>();
            if (ObjectUtil.isNotEmpty(request.getLat()) && ObjectUtil.isNotEmpty(request.getLon())) {
                queryWrapper.select("building_gaode.*, " +
                                String.format("round(ST_Distance_Sphere(POINT(%s, %s), POINT(lon, lat))/1000,2) AS distance", request.getLon(), request.getLat()))
                        .orderByAsc("distance");
            }
            queryWrapper.lambda().eq(BuildingGaode::getKwStatus, 0).orderByDesc(BuildingGaode::getUpdateTime);
            if (ObjectUtil.isNotEmpty(request.getKeyword())) {
                queryWrapper.lambda().and(
                        lwp -> lwp.like(BuildingGaode::getPoiName, request.getKeyword()).
                                or().
                                like(BuildingGaode::getMultiAddrChn, request.getKeyword())
                );
            }
            // 对前端网格列表进行处理
            String[] gridIdStr = request.getGridId().split(",");
            List<String> gridIdList = Arrays.asList(gridIdStr);
            // 工号为省级工号
            if (!RegionNumPermissionEnum.PROVINCE.name().equals(request.getGridType()) || gridIdList.size() != 1) {
                queryWrapper.lambda().
                        and(
                                wp -> wp.in(BuildingGaode::getRegionCityCode, gridIdList).
                                        or().
                                        in(BuildingGaode::getRegionDistrictsCode, gridIdList).
                                        or().
                                        in(BuildingGaode::getGridId, gridIdList)
                        );
                log.debug("BuildingGaodeServiceImpl queryList end not province");
            }
            return buildingGaodeMapper.selectPage(page, queryWrapper);
        }
        log.debug("BuildingGaodeServiceImpl queryList end");
        return new Page<>();
    }

    @Override
    public IPage<BuildingGaode> proofreaded(BuildingProofreadQueryRequest request) {
        if (CharSequenceUtil.isNotEmpty(request.getGridType()) && RegionNumPermissionEnum.pitchRegion(request.getGridType())) {
            QueryWrapper<BuildingGaode> queryWrapper = new QueryWrapper<>();
            if (ObjectUtil.isNotEmpty(request.getLat()) && ObjectUtil.isNotEmpty(request.getLon())) {
                queryWrapper.select("building_gaode.*, " +
                                String.format("round(ST_Distance_Sphere(POINT(%s, %s), POINT(lon, lat))/1000,2) AS distance", request.getLon(), request.getLat()))
                        .orderByAsc("distance");
            }
            queryWrapper.lambda().eq(BuildingGaode::getKwStatus, 1).orderByDesc(BuildingGaode::getUpdateTime);

            //多网格查询
            String[] gridIdStr = request.getGridId().split(",");
            List<String> gridIdList = Arrays.asList(gridIdStr);

            //创建分页对象
            IPage<BuildingGaode> page = new Page<>(request.getPageNum(), request.getPageSize());
            if (ObjectUtil.isNotEmpty(request.getKeyword())) {
                queryWrapper.lambda().and(
                        lwp -> lwp.like(BuildingGaode::getPoiName, request.getKeyword()).
                                or().
                                like(BuildingGaode::getMultiAddrChn, request.getKeyword())
                );
            }
            // 工号为省级工号
            if (!RegionNumPermissionEnum.PROVINCE.name().equals(request.getGridType()) || gridIdList.size() != 1) {
                queryWrapper.lambda().
                        and(
                                wp -> wp.in(BuildingGaode::getRegionCityCode, gridIdList).
                                        or().
                                        in(BuildingGaode::getRegionDistrictsCode, gridIdList).
                                        or().
                                        in(BuildingGaode::getGridId, gridIdList)
                        );
            }
            return buildingGaodeMapper.selectPage(page, queryWrapper);
        }
        return new Page<>();
    }

    @Override
    public BuildingProofread queryProofreadByGId(BuildingProofreadQueryRequest request) {
        String gaodeId = request.getGaodeId();
        log.debug("queryProofreadByGId begin gaodeId is :{}", gaodeId);
        LambdaQueryWrapper<BuildingProofread> queryWapper = new LambdaQueryWrapper<>();
        queryWapper.eq(BuildingProofread::getGaodeId, gaodeId);
        return proofreadMapper.selectOne(queryWapper);
    }

    @Override
    public String proofread(BuildingProofreadRequest request) {
        log.debug("proofread begin：{}", JSONUtil.toJsonStr(request));
        RLock rlock = redissonClient.getLock(RedisContant.BUILDING_PROOF_LOCK);
        try {
            boolean lockRes = rlock.tryLock(10, TimeUnit.SECONDS);
            if (lockRes) {
                //处理高德表
                BuildingGaode buildingGaode = getBaseMapper().selectById(request.getGaodeId());
                buildingGaode.setKwStatus(1); //更新勘误状态

                if (request.getGridBelongStatus() == 0) { // 优化：若勘误过程，站点不属于本网格，选择应属网格，勘误状态更新为：未勘误
                    buildingGaode.setKwStatus(ExpressStatus.UN_PROOFED);
                    // 自动匹配至应归属网格、应归属区县  request.getBelongGrid()
                    buildingGaode.setGridName(request.getBelongGrid());
                    buildingGaode.setRegionDistricts(request.getBelongCounty());
                    buildingGaode.setGridId(request.getBelongGridCode());
                    buildingGaode.setRegionDistrictsCode(request.getBelongCountyCode());
                    // 进行日志记录入库
                    OperationLog operationLog = OperationLog.builder()
                            .logAccount(request.getKwPhone())
                            .operationId(request.getId())
                            .logType(0) // 0:应属网格操作
                            .operateType(4)  //4:学校
                            .createTime(DateUtil.date()).build();
                    operationLogMapper.insert(operationLog);
                }

                buildingGaode.setKwPhone(request.getKwPhone());
                buildingGaode.setUpdateTime(DateUtil.date());//更新修改(勘误)时间
                buildingGaodeMapper.updateById(buildingGaode);

                //处理BuildingProofread 表
                BuildingProofread buildingProofread =
                        proofreadMapper.selectOne(new LambdaQueryWrapper<BuildingProofread>()
                                .eq(BuildingProofread::getId, request.getId()));
                BuildingProofread info = new BuildingProofread();
                // 新增场景
                if (ObjectUtil.isEmpty(buildingProofread)) {
                    BeanUtil.copyProperties(request, info);
                    info.setId(null);
                    info.setCreateTime(DateUtil.date());
                    info.setUpdateTime(DateUtil.date());
                    proofreadMapper.insert(info);
                } else {
                    // 初始化的时候已做映射
                    BeanUtil.copyProperties(request, info);
                    info.setUpdateTime(DateUtil.date()); //勘误修改时间记录
                    proofreadMapper.updateById(info);
                }
                // 上传图片
                attachmentService.uploadPicture(request.getPictureList(), request.getId());
                log.debug("proofread end");
                return "勘误成功";
            }
            log.debug("proofread end get no lock");
            return "系统繁忙，请稍后再试";
        } catch (Exception e) {
            throw new CommonException("勘误失败");
        } finally {
            rlock.unlock(); // 释放redis锁
        }
    }

    @Override
    public IPage<BuildingGaode> addConfidence(IPage<BuildingGaode> unProofread) {
        List<BuildingGaode> buildingGaodeList = unProofread.getRecords();
        buildingGaodeList.forEach(item -> {
            String poiId = item.getPoiId();
            LambdaQueryWrapper<BuildingGather> gatherQueryWapper = new LambdaQueryWrapper<>();
            gatherQueryWapper.eq(BuildingGather::getGdPoiId, poiId);
            List<BuildingGather> buildingGatherList = gatherMapper.selectList(gatherQueryWapper);
            // 置信度默认设置 3  有关联则用具体的关联值
            item.setConfidence(new BigDecimal(3));
            if (ObjectUtil.isNotEmpty(buildingGatherList)) {
                item.setConfidence(new BigDecimal(buildingGatherList.get(0).getConfidence()));
            }
        });
        return unProofread;
    }

    @Override
    public IPage<BuildingGather> queryGatherByGd(BuildingProofreadQueryRequest request) {
        log.debug("queryGatherByGd begin");
        QueryWrapper<BuildingGather> queryWrapper = new QueryWrapper<>();
        //创建分页对象
        IPage<BuildingGather> page = new Page<>(request.getPageNum(), request.getPageSize());
        if (ObjectUtil.isNotEmpty(request.getLat()) && ObjectUtil.isNotEmpty(request.getLon())) {
            queryWrapper.select("building_gather.*, " +
                            String.format("round(ST_Distance_Sphere(POINT(%s, %s), POINT(lon, lat))/1000,2) AS distance", request.getLon(), request.getLat()))
                    .orderByAsc("distance");
        }
        if (ObjectUtil.isNotEmpty(request.getKeyword())) {
            queryWrapper.lambda().and(
                    lwp -> lwp.like(BuildingGather::getAddress, request.getKeyword())
            );
        }
        return gatherMapper.selectPage(page, queryWrapper);
    }
}
